use std::{fs::File, io::Read, path::PathBuf, thread::sleep as stdsleep, time::Duration};

use ssh2::{ErrorCode, FileStat};
use wtask_base::{
    error::{WError, WResult},
    export::{Local, log_error, log_info},
    logs::TIME_FORMAT_TIMELINE,
    machine::{CMDArgsConfigMachine, CMDArgsConfigSsh, WTASK_MACHINE_RUNNING_FILE},
    update::{WTASK_FILE_INSTSLL_PREFIX, dir_update, get_local_version}
};



fn ssh_install_machine_inner_exec(sess: &ssh2::Session, cmd: &str) -> Result<String, ssh2::Error> {
    let mut channel = sess.channel_session()?;
    channel.exec(cmd)?;
    let mut res = String::new();
    let _ = channel.read_to_string(&mut res);
    channel.wait_close()?;
    Ok(res.trim().to_owned())
}


fn ssh_install_machine_inner(sess: ssh2::Session, deep: u8, force: bool) -> Result<(u32, String), ssh2::Error> {
    let sftp = sess.sftp().unwrap();

    if !force {
        log_info!("Check if running ...");
        let dir_wtask_root = ssh_install_machine_inner_exec(&sess, "echo $WTASK_ROOT")?;
        // 看是否已有运行
        let file_running = PathBuf::from(dir_wtask_root).join(WTASK_MACHINE_RUNNING_FILE);
        if let Ok(mut f) = sftp.open(&file_running) {
            log_info!("Reading config: {file_running:?}");
            // 文件已经存在，程序正在运行，读取文件返回数据
            let mut data = String::new();
            f.read_to_string(&mut data).unwrap();
            let data_split = data.trim().split_once("\n").unwrap();
            let machine_port = data_split.1.parse::<u32>().unwrap();
            let machine_token = data_split.0.to_string();
            return Ok((machine_port, machine_token));
        }
    }

    // check unix
    if sftp.open("/bin/sh").is_err() {
        return Err(ssh2::Error::new(ErrorCode::Session(0), "Shell not found"));
    }

    if deep > 0 {
        return Err(ssh2::Error::new(ErrorCode::Session(0), "Install failed"));
    }

    let dir_home = ssh_install_machine_inner_exec(&sess, "echo $HOME")?;

    let dir_local = dir_update().unwrap();
    let dir_install = PathBuf::from(dir_home).join(format!("wtask_temp.{}", Local::now().format(TIME_FORMAT_TIMELINE)));
    let dir_install_str = dir_install.to_str().unwrap();
    let file_install_version = get_local_version();
    let file = "wtask_install";
    {
        // 复制文件
        sftp.mkdir(&dir_install, 0o755)?;
        log_info!("Remote install dir: {dir_install_str:?}");

        // file_version
        let file1 = format!("{}{}.zip", WTASK_FILE_INSTSLL_PREFIX.as_str(), file_install_version);
        log_info!("Uploading file: {file1}");
        let p_local = dir_local.join(&file1);
        let mut f_local = File::open(&p_local).unwrap();
        let p = dir_install.join(file1);
        let mut f_remote = sftp.create(&p).unwrap();
        std::io::copy(&mut f_local, &mut f_remote).unwrap();

        // file_install
        log_info!("Uploading file: {file}");
        let p_local = dir_local.parent().unwrap().join("bin").join(file);
        let mut f_local = File::open(&p_local).unwrap();
        let p = dir_install.join(file);
        let mut f_remote = sftp.create(&p).unwrap();
        std::io::copy(&mut f_local, &mut f_remote).unwrap();
        sftp.setstat(&p, FileStat {
            size: None,
            uid: None,
            gid: None,
            perm: Some(0o755),
            atime: None,
            mtime: None
        })?;
    }
    
    
    // 执行安装命令
    let cmd = format!("cd {dir_install_str} && ./{file} --dir {dir_install_str} --ver {file_install_version} --del");

    log_info!("Installing: {cmd}");
    
    let log_temp = ssh_install_machine_inner_exec(&sess, &cmd)?;
    log_info!("Remote: {log_temp}");

    if log_temp.is_empty() {
        return Err(ssh2::Error::new(ErrorCode::Session(0), "Install cmd no result, maybe cannot run"));
    }

    stdsleep(Duration::from_secs(10));

    ssh_install_machine_inner(sess, deep+1, false)
}




pub fn ssh_install_machine<F: FnOnce() -> String>(config_ssh: CMDArgsConfigSsh, password_func: F) -> WResult<CMDArgsConfigMachine> {
    // run server by ssh, and reture token
    let tcp = std::net::TcpStream::connect(format!("{}:{}", config_ssh.ssh_ip, config_ssh.ssh_port)).unwrap();
    let mut sess = ssh2::Session::new().unwrap();
    sess.set_timeout(10000);
    sess.set_tcp_stream(tcp);
    sess.handshake().expect("SSH Connect Error");
    
    log_info!("IP: {}, Port: {}, User: {}", config_ssh.ssh_ip, config_ssh.ssh_port, config_ssh.ssh_user);
    let sess_res = match config_ssh.private_file {
        Some(private_file) if private_file.exists() => {
            log_info!("Private file: {private_file:?}");
            sess.userauth_pubkey_file(&config_ssh.ssh_user, None, &private_file, None)
        },
        _ => {
            let password = password_func();
            sess.userauth_password(&config_ssh.ssh_user, &password)
        }
    };
    let pass = sess_res.is_ok() && sess.authenticated();
    if !pass {
        log_error!("SSH Auth Eror: {sess_res:?}");
        return Err(WError::NetError(format!("SSH Auth Eror: {sess_res:?}")));
    }
    
    match ssh_install_machine_inner(sess, 0, config_ssh.force) {
        Ok((machine_port, machine_token)) => {
            Ok(CMDArgsConfigMachine {
                machine_ip: config_ssh.ssh_ip,
                machine_port,
                proxy: false,
                machine_token,
            })
        },
        Err(e) => {
            Err(WError::NetError(format!("SSH Install Machine Eror: {:?}", e.message())))
        }
    }
}